
/* chpciintf:Characte device driver interface to an installed PCI Network interace card 
 * Programmed by: Lokesh V
 * 
 * Version: 0.2 (version 0.1: used old manual method probing and updated hotpluging probe method)
 * 
 * Purpose: To retrive PCI Configuration space details for an installed PCI NIC
 * (Intel 82578DC and Intel 82579V Gigabit Ethernet cards) through a character interface
 * driver and updates Link status and mac address to the user space application using 
 * read (mac address) and  ioctl (linkstatus)
 * 
 * Important: Before insmoding the driver, remove the intel driver (e1000e) from the system.
 * 
 * This code is distributed under Version 2 of the GNU General Public  
 * License, which you should have received with the source.
 *
 */

#include <linux/module.h> 
#include <linux/pci.h>    
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

MODULE_AUTHOR("Lokesh V");
MODULE_LICENSE ("GPL v2");

#define VENDOR_ID 0X8086 	// Intel's Vendor ID
//#define DEVICE_ID 0x10F0 	// 82578 Gb ethernet card
#define DEVICE_ID 0x1503  	// 82579 Gb ethernet card

#define MAC_ADDRESS_OFFSET_START 0x40 	// Mac address offset, BAR 0
#define MAC_ADDRESS_OFFSET_END	0x45

#define MYDEV_NAME "mycdrv"

#define STATUS  0x00008 		// Device Status Register address, BAR0
#define E1000_STATUS_LU	0x00000002 	// Contains Link Status, 0b = No link, 1b= Link established, BAR0

#ifndef bool
#define bool _Bool
#endif 

static unsigned long ulMemoryBase0, ulMemoryBase0End, ulMemoryBase0_len;
static unsigned long ulMemoryBase1, ulMemoryBase1End, ulMemoryBase1_len;
static unsigned long iobase, iobaseend, iobase_len;
static unsigned long ulResFlags0, ulResFlags1, iobaseResFlags;

static dev_t first;
static unsigned int count = 1;
static int my_major = 700, my_minor = 0;
static struct cdev *my_cdev;

/* PCI Device ids*/

static struct pci_device_id ids[] = {
	{ PCI_DEVICE(VENDOR_ID, DEVICE_ID), },
	{ 0, }
};
MODULE_DEVICE_TABLE(pci, ids);

/* Function to read Link status of NIC card, whether the cable is connected or disconnected physically.
 * It reads by remapping the IO region to the virtual memory @ioaddress
*/

bool getlinkstatus(void)
{
	unsigned char temp;
	void __iomem *ioaddress;
	ioaddress = ioremap(ulMemoryBase0, ulMemoryBase0_len);
	temp = readb(ioaddress + STATUS);
	if (likely (temp & E1000_STATUS_LU))
		return true;
	else 
		return false;
	iounmap(ioaddress);
}

/* Probe funciton called when the devices are connected.Its called after pci_register_driver()
 * This funcions extracts basic PCI COnfiguration details.
 * Important: Before insmoding the driver, e1000e driver has to unloaded.
*/

static int pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
	/* Do probing type stuff here.  
	 * Like calling request_region();
	 */
	
	pci_enable_device(dev);
	pci_enable_device_mem(dev);
	printk (KERN_DEBUG "\n%s :Probe is called.\n",__FUNCTION__);
	
	//Region 0
	ulMemoryBase0 = pci_resource_start(dev, 0);
	ulMemoryBase0End = pci_resource_end(dev, 0);
	ulMemoryBase0_len = pci_resource_len(dev, 0);
	ulResFlags0 = pci_resource_flags(dev, 0);
	
	//Region 1              
	ulMemoryBase1 = pci_resource_start(dev, 1);
	ulMemoryBase1End = pci_resource_end(dev, 1);
	ulMemoryBase1_len = pci_resource_len(dev, 1);
	ulResFlags1 = pci_resource_flags(dev, 1);

	//Region 2 I/O Ports 
	iobase = pci_resource_start(dev, 2);
	iobaseend = pci_resource_end(dev, 2);
	iobase_len = pci_resource_len(dev, 2);
	iobaseResFlags = pci_resource_flags(dev, 2);
	
	if (ulResFlags0 & IORESOURCE_MEM) {
		printk(KERN_DEBUG "Region 0 is MMIO.\n");
		printk(KERN_DEBUG "Region 0: Memory at %lx\n", ulMemoryBase0);
	}
	
	if (ulResFlags1 & IORESOURCE_MEM) {
		printk(KERN_DEBUG "Region 1 is MMIO.\n");
		printk(KERN_DEBUG "Region 1: Memory at %lx\n", ulMemoryBase1);
	}

	if (iobaseResFlags & IORESOURCE_IO) {
		printk(KERN_DEBUG "Region 2 is IO Mapped IO.\n");
		printk(KERN_DEBUG "Region 2: I/O Ports at %lx\n", iobase);
	}
	printk (KERN_DEBUG "Interrupt line used = %d\n",dev->irq);
	return 0;
	
}

/* PCI Remove funiton whcih disables the pci device*/

static void pci_remove(struct pci_dev *dev)
{
	/* clean up any allocated resources and stuff here.
	 * like call release_region();
	 */
	pci_disable_device(dev);
}

/* pci_driver structure which stores methods, ids tables.*/

static struct pci_driver pci_driver = {
	.name = "pcicdrv",
	.id_table = ids,
	.probe = pci_probe,
	.remove = pci_remove,
};


/* Character device driver open method when the user space program opens the char device*/

static int mycdrv_open (struct inode *inode, struct file *file)
{
    	static int counter = 0;
	printk (KERN_INFO "Attempting to open device: %s: Major # = %d, Minor # = %d\n", MYDEV_NAME,imajor(inode), iminor(inode));
	counter++;
	pr_info("%s have opened %d times since being loaded\n", MYDEV_NAME, counter);
	pr_info ("Moudle reference count = %d\n", (int)module_refcount (THIS_MODULE));

    	return 0;
}

/* Relase method for character device driver*/

static int mycdrv_release (struct inode *inode, struct file *file)
{
    	printk (KERN_INFO "Closing device: %s\n", MYDEV_NAME);
    	return 0;
}

/* Read method implementation for the char driver. It is called when the user space calls read() system call
 * Retrives Mac address for the Network Interface card installed. It reads from BAR @ offset 0x40 -0x45
*/

static ssize_t mycdrv_read (struct file *file, char __user * buf, size_t lbuf, loff_t * ppos)
{
	void __iomem *ioaddress;
	unsigned char mac_address[6];
    	
	pr_info ("Reading Mac address from the pci network card device..\n");
	ioaddress = ioremap(ulMemoryBase0, ulMemoryBase0_len);
	mac_address[0] = readb(ioaddress+MAC_ADDRESS_OFFSET_START+0);
	mac_address[1] = readb(ioaddress+MAC_ADDRESS_OFFSET_START+1);
	mac_address[2] = readb(ioaddress+MAC_ADDRESS_OFFSET_START+2);
	mac_address[3] = readb(ioaddress+MAC_ADDRESS_OFFSET_START+3);
	mac_address[4] = readb(ioaddress+MAC_ADDRESS_OFFSET_START+4);
	mac_address[5] = readb(ioaddress+MAC_ADDRESS_OFFSET_END);
	
	pr_info ("MAC Address = %x:%x:%x:%x:%x:%x\n", mac_address[0],mac_address[1],mac_address[2],mac_address[3], mac_address[4],mac_address[5]);
    	copy_to_user (buf, mac_address, 6);
	
	iounmap (ioaddress);
    	return 0;
}

/* Charcter driver write method. Does nothing.*/

static ssize_t mycdrv_write (struct file *file, const char __user * buf, size_t lbuf, loff_t * ppos)
{
    	printk (KERN_INFO "Writing method\n");
	return 0;
}

/* Ioctl method which returns Link status to the user application when it calls icotl */

static long mycdrv_unlocked_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
	
	bool status;
	char *buf;
	pr_info ("Reading Link Status\n");
	
	status = getlinkstatus();
	buf = (char*) arg;
	pr_info ("LINK STATUS = %d\n",status);
	pr_info ("1 = Connected, 0 = Disconnected \n");
	copy_to_user (buf, &status, 1);
	return 0;
	
	
}

/* character driver fops structure*/

static const struct file_operations mycdrv_fops = {
	.owner = THIS_MODULE,
    	.read = mycdrv_read,
  	.write = mycdrv_write,
 	.open = mycdrv_open,
    	.release = mycdrv_release,
    	.unlocked_ioctl = mycdrv_unlocked_ioctl, //use ioctl for older kernels
};

/* MOdule init. register pci driver, char driver*/

static int pci_init(void)
{

	int ret;
	ret =pci_register_driver(&pci_driver);
	if (ret < 0) {
		pr_info ("PCI Registration unsuccessful.\n");
	}

    	first = MKDEV (my_major, my_minor);
    	if (register_chrdev_region (first, count, MYDEV_NAME) < 0) {
        	printk (KERN_ERR "failed to register character device region\n");
        	return -1;
    	}
    	if (!(my_cdev = cdev_alloc ())) {
        	printk (KERN_ERR "cdev_alloc() failed\n");
        	unregister_chrdev_region (first, count);
        	return -1;
    	}
    	cdev_init (my_cdev, &mycdrv_fops);

    	if (cdev_add (my_cdev, first, count) < 0) {
        	printk (KERN_ERR "cdev_add() failed\n");
        	cdev_del (my_cdev);
        	unregister_chrdev_region (first, count);
        	return -1;
    	}
	return 0;

}


/* Driver cleanup module */

static void pci_exit(void)
{
	pci_unregister_driver(&pci_driver);
	pr_info ("PCI unregistration successfull.\n");
	if (my_cdev)
        	cdev_del (my_cdev);
    	unregister_chrdev_region (first, count);
    	printk (KERN_INFO "%s Char Device unregistered sucessfully.\n",MYDEV_NAME);
}

module_init(pci_init);
module_exit(pci_exit);
